import XLSX from 'yxg-xlsx-style';
export class ExportExcel {
  // 默认的表格样式
  defaultCellStyle = {
    border: {
      top: { style: 'thin', color: { rgb: '000000' } },
      left: { style: 'thin', color: { rgb: '000000' } },
      bottom: { style: 'thin', color: { rgb: '000000' } },
      right: { style: 'thin', color: { rgb: '000000' } }
    },
    alignment: {
      horizontal: 'center',
      vertical: 'center'
    }
  };
  // 定义数据样式方法
  styleCell: any = null;
  /**
   * 导出
   * @param tableData 导出的数据
   * @param tableHeader excel头部
   * @param addHeadMerges 增加合并方法内没有的合并规则
   * @param delheadKey 删除合并方法内不需要的合并规则
   * @param styleCell 数据样式设置
   * @param w 列宽
   */
  exportData(tableData: any, tableHeader: any, name = '表格', styleCell: any, w = [], addHeadMerges = [], delFun: Function) {
    if (typeof styleCell === 'function') {
      this.styleCell = styleCell;
    }

    if (!Array.isArray(tableData) || tableData.length < 1) {
      // throw { type: "error", message: "请选择需要导出的数据！" };
    }
    const sheetName = name;
    // excel表头
    const excelHeader = this.buildHeader(tableHeader);
    // 头部行数，用来固定表头
    const headerRows = excelHeader.length;
    // 提取数据
    const dataList = this.extractData(tableData, tableHeader);
    excelHeader.push(...dataList, []);

    // 计算合并
    const merges = this.doMerges(excelHeader, addHeadMerges, delFun);
    // 生成sheet
    const ws = this.aoa_to_sheet(excelHeader, headerRows);
    // 单元格合并
    ws['!merges'] = merges;
    // 头部冻结
    ws['!freeze'] = {
      xSplit: '1',
      ySplit: '' + headerRows,
      topLeftCell: 'B' + (headerRows + 1),
      activePane: 'bottomRight',
      state: 'frozen'
    };
    // 列宽
    ws['!cols'] = w || [];
    const workbook: any = {
      SheetNames: [sheetName],
      Sheets: {}
    };
    workbook.Sheets[sheetName] = ws;
    // excel样式
    const wopts = {
      bookType: 'xlsx',
      bookSST: false,
      type: 'binary',
      cellStyles: true
    };

    // 表格样式;
    const wbout = XLSX.write(workbook, wopts, { defaultCellStyle: this.defaultCellStyle });
    const blob = new Blob([this.s2ab(wbout)], { type: 'application/octet-stream' });
    this.openDownloadXLSXDialog(blob, sheetName + '.xlsx');

    // window.saveAs(blob, sheetName + '.xlsx')
  }

  /**
   * 构建excel表头
   * @param revealList 列表页面展示的表头
   * @returns {[]} excel表格展示的表头
   */
  buildHeader(revealList: any[]) {
    const excelHeader: any[] = [];
    // 构建生成excel表头需要的数据结构
    this.getHeader(revealList, excelHeader, 0, 0);
    // 多行表头长短不一，短的向长的看齐，不够的补上行合并占位符
    const max = Math.max(...excelHeader.map(a => a.length));
    excelHeader.filter(e => e.length < max).forEach(e => this.pushRowSpanPlaceHolder(e, max - e.length));
    return excelHeader;
  }

  /**
   * 生成头部
   * @param headers 展示的头部
   * @param excelHeader excel头部
   * @param deep 深度
   * @param perOffset 前置偏移量
   * @returns {number}  后置偏移量
   */
  getHeader(headers: any[], excelHeader: any[], deep: number, perOffset: number) {
    let offset = 0;
    let cur = excelHeader[deep];
    if (!cur) {
      cur = excelHeader[deep] = [];
    }
    // 填充行合并占位符
    this.pushRowSpanPlaceHolder(cur, perOffset - cur.length);
    for (let i = 0; i < headers.length; i++) {
      const head = headers[i];
      cur.push(head.label);
      if (head?.childern && Array.isArray(head.childern) && head.childern.length > 0) {
        const childOffset = this.getHeader(head.childern, excelHeader, deep + 1, cur.length - 1);
        // 填充列合并占位符
        this.pushColSpanPlaceHolder(cur, childOffset - 1);
        offset += childOffset;
      } else {
        offset++;
      }
    }
    return offset;
  }

  /**
   * 根据选中的数据和展示的列，生成结果
   * @param selectionData
   * @param revealList
   */
  extractData(selectionData: any[], revealList: any[]) {
    // 列
    const headerList = this.flat(revealList);
    // 导出的结果集
    const excelRows: any = [];
    // 如果有child集合的话会用到
    const dataKeys = new Set(Object.keys(selectionData[0]));
    selectionData.some(e => {
      if (e.childern && e.childern.length > 0) {
        const childKeys = Object.keys(e.childern[0]);
        for (let i = 0; i < childKeys.length; i++) {
          dataKeys.delete(childKeys[i]);
        }
        return true;
      }
    });
    this.flatData(selectionData, (list: any) => {
      excelRows.push(...this.buildExcelRow(dataKeys, headerList, list));
    });
    return excelRows;
  }

  buildExcelRow(mainKeys: any, headers: any[], rawDataList: any[]) {
    // 合计行
    const sumCols: any = [];
    // 数据行
    const rows = [];
    for (let i = 0; i < rawDataList.length; i++) {
      const cols = [];
      const rawData = rawDataList[i];
      // 提取数据
      for (let j = 0; j < headers.length; j++) {
        const header = headers[j];
        // 父元素键需要行合并
        if (rawData['rowSpan'] === 0 && mainKeys.has(header.prop)) {
          cols.push('!$ROW_SPAN_PLACEHOLDER');
        } else {
          let value;
          if (typeof header.exeFun === 'function') {
            value = header.exeFun(rawData);
          } else {
            value = rawData[header.prop];
          }
          cols.push(value);
          // 如果该列需要合计,并且是数字类型
          if (header['summable'] && typeof value === 'number') {
            sumCols[j] = (sumCols[j] ? sumCols[j] : 0) + value;
          }
        }
      }
      rows.push(cols);
    }
    // 如果有合计行
    // if (sumCols.length > 0) {
    //   rows.push(...this.sumRowHandle(sumCols));
    // }
    return rows;
  }
  // sumRowHandle(sumCols) {
  //   // TODO
  //   return [];
  // }

  /**
   * @description: 需要合并的数据
   * @param {*} arr
   * @return {*}
   */
  doMerges(arr: any, addArr: any, delFun: Function) {
    // 要么横向合并 要么纵向合并
    const deep = arr.length;
    let merges = [];
    for (let y = 0; y < deep; y++) {
      // 先处理横向合并
      const row = arr[y];
      let colSpan = 0;
      for (let x = 0; x < row.length; x++) {
        if (row[x] === '!$COL_SPAN_PLACEHOLDER') {
          row[x] = undefined;
          if (x + 1 === row.length) {
            merges.push({ s: { r: y, c: x - colSpan - 1 }, e: { r: y, c: x } });
          }
          colSpan++;
        } else if (colSpan > 0 && x > colSpan) {
          merges.push({ s: { r: y, c: x - colSpan - 1 }, e: { r: y, c: x - 1 } });
          colSpan = 0;
        } else {
          colSpan = 0;
        }
      }
    }
    // 再处理纵向合并
    const colLength = arr[0].length;
    for (let x = 0; x < colLength; x++) {
      let rowSpan = 0;
      for (let y = 0; y < deep; y++) {
        if (arr[y][x] === '!$ROW_SPAN_PLACEHOLDER') {
          arr[y][x] = undefined;
          if (y + 1 === deep) {
            merges.push({ s: { r: y - rowSpan, c: x }, e: { r: y, c: x } });
          }
          rowSpan++;
        } else if (rowSpan > 0 && y > rowSpan) {
          merges.push({ s: { r: y - rowSpan - 1, c: x }, e: { r: y - 1, c: x } });
          rowSpan = 0;
        } else {
          rowSpan = 0;
        }
      }
    }

    // 直接添加合并规则
    if (addArr.length) {
      merges.push(...addArr);
    }

    // 直接删除不需要的合并规则
    if (typeof delFun === 'function') {
      merges = delFun(merges);
    }
    console.log(merges, 'merges后');
    return merges;
  }

  /**
   * 从github复制过来的
   */
  aoa_to_sheet(data: any[], headerRows: any) {
    const ws: any = {};
    const range = { s: { c: 10000000, r: 10000000 }, e: { c: 0, r: 0 } };
    for (let R = 0; R !== data.length; ++R) {
      for (let C = 0; C !== data[R].length; ++C) {
        if (range.s.r > R) {
          range.s.r = R;
        }
        if (range.s.c > C) {
          range.s.c = C;
        }
        if (range.e.r < R) {
          range.e.r = R;
        }
        if (range.e.c < C) {
          range.e.c = C;
        }
        // / 这里生成cell的时候，使用上面定义的默认样式
        const cell: any = {
          v: data[R][C] || '',
          s: Object.assign({}, this.defaultCellStyle) // 这里是因为防止浅复制导致数据地址指向同一数据
        };

        // 头部列表加边框
        if (R < headerRows) {
          const style = Object.assign(cell.s, {
            border: {
              top: { style: 'thin', color: { rgb: '000000' } },
              left: { style: 'thin', color: { rgb: '000000' } },
              bottom: { style: 'thin', color: { rgb: '000000' } },
              right: { style: 'thin', color: { rgb: '000000' } }
            },
            font: {
              sz: 12,
              bold: true
            },
            fill: {
              patternType: 'solid',
              fgColor: { theme: 3, tint: 0.3999755851924192, rgb: 'F5F7FA' },
              bgColor: { theme: 7, tint: 0.3999755851924192, rgb: 'ffff00' }
            }
          });
          cell.s = style;
        }
        const cell_ref = XLSX.utils.encode_cell({ c: C, r: R });
        if (typeof cell.v === 'number') {
          cell.t = 'n';
        } else if (typeof cell.v === 'boolean') {
          cell.t = 'b';
        } else {
          cell.t = 's';
        }

        // 插入表格样式
        if (typeof this.styleCell === 'function') {
          // 插入数据样式
          const styleCell = this.styleCell(R, C, cell);
          if (typeof styleCell === 'object') {
            cell.s = Object.assign(cell.s, styleCell);
          }
        }

        ws[cell_ref] = cell;
      }
    }
    if (range.s.c < 10000000) {
      ws['!ref'] = XLSX.utils.encode_range(range);
    }
    return ws;
  }
  /**
   * 填充行合并占位符
   * */
  pushRowSpanPlaceHolder(arr: any[], count: number) {
    for (let i = 0; i < count; i++) {
      arr.push('!$ROW_SPAN_PLACEHOLDER');
    }
  }
  // 填充列合并占位符
  pushColSpanPlaceHolder(arr: any[], count: number) {
    for (let i = 0; i < count; i++) {
      arr.push('!$COL_SPAN_PLACEHOLDER');
    }
  }
  /**
   * @description:铺平数组
   * @param {*} list 表格数据
   * @param {*} eachDataCallBack 构建的行数据
   * @return {*}
   */
  flatData(list: any[], eachDataCallBack: Function) {
    const resultList = [];
    for (let i = 0; i < list.length; i++) {
      const data = list[i];
      const rawDataList = [];
      // 每个子元素都和父元素合并成一条数据
      if (data.childern && data.childern.length > 0) {
        for (let j = 0; j < data.childern.length; j++) {
          delete data.childern[j].bsm;
          const copy = Object.assign({}, data, data.childern[j]);
          rawDataList.push(copy);
          copy['rowSpan'] = j > 0 ? 0 : data.childern.length;
        }
      } else {
        data['rowSpan'] = 1;
        rawDataList.push(data);
      }
      resultList.push(...rawDataList);
      if (typeof eachDataCallBack === 'function') {
        eachDataCallBack(rawDataList);
      }
    }
    return resultList;
  }
  // 扁平头部
  flat(revealList: any[]) {
    const result: any = [];
    revealList.forEach(e => {
      if (e?.childern) {
        result.push(...this.flat(e.childern));
      } else if (e?.exeFun) {
        result.push(e);
      } else if (e?.prop) {
        result.push(e);
      }
    });
    return result;
  }
  s2ab(s: any) {
    const buf = new ArrayBuffer(s.length);
    const view = new Uint8Array(buf);
    for (let i = 0; i !== s.length; ++i) {
      view[i] = s.charCodeAt(i) & 0xff;
    }
    return buf;
  }
  openDownloadXLSXDialog(url: any, saveName: string) {
    if (typeof url === 'object' && url instanceof Blob) {
      url = URL.createObjectURL(url); // 创建blob地址
    }
    let aLink = document.createElement('a');
    aLink.href = url;
    aLink.download = saveName || ''; // HTML5新增的属性，指定保存文件名，可以不要后缀，注意，file:///模式下不会生效
    let event;
    if (window.MouseEvent) {
      event = new MouseEvent('click');
    } else {
      event = document.createEvent('MouseEvents');
      event.initMouseEvent('click', true, false, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
    }
    aLink.dispatchEvent(event);
  }
}
